#include "MD5HashHelper.h"
#include "GeneralHashFunctions.h"

#ifdef _USING_WINCRYPT
#include <windows.h>
#include <Wincrypt.h>
#else
#include "MD5.h"
#include <stdio.h>
#endif

LIB_UTIL_NAMESPACE_BEGIN

#define BUFSIZE		1024
#define MD5LEN		16
const static char rgbDigits[] = "0123456789ABCDEF";

//unsigned int hash_1(char* str) 
//{ 
//	register unsigned int h = 0; 
//	register unsigned char* p = (unsigned char*)str; 
//
//	for(; *p; p++) 
//		h = 31 * h + *p; 
//	return h; 
//}
//
//unsigned int hash_2(char* str) 
//{ 
//	register unsigned int h = 0, g; 
//	register unsigned char* p = (unsigned char*)str; 
//
//	while(*p) { 
//		h = (h << 4) + *p++; 
//		if ((g = (h & 0xF0000000))) 
//		{ 
//			h = h ^ (g >> 24); 
//			h = h ^ g; 
//		} 
//	} 
//	return h; 
//} 

long long GetHashValue1(char* sz)
{
	/*long long value = hash_1(sz);
	value <<= 32;
	value |= hash_2(sz);
	return value;*/
	long long value = RSHash(sz);
	value <<= 32;
	value |= JSHash(sz);
	return value;
}

long long GetHashValue2(char* sz)
{
	/*long long value = hash_1(sz);
	value <<= 32;
	value |= hash_2(sz);
	return value;*/
	long long value = SDBMHash(sz);
	value <<= 32;
	value |= DJBHash(sz);
	return value;
}

MD5HashHelper::MD5HashHelper(void)
{
}

MD5HashHelper::~MD5HashHelper(void)
{
}

char* MD5HashHelper::GetDataBufferHashString(const tDataBuffer& buffer)
{
	if(buffer.data == NULL)
		return NULL;

#ifdef _USING_WINCRYPT
	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	BYTE rgbHash[MD5LEN];
	DWORD cbHash = 0;
	
	// Get handle to the crypto provider
	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
		return NULL;

	if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
	{
		CryptReleaseContext(hProv, 0);
		return NULL;
	}

	if(!CryptHashData(hHash, buffer.data, buffer.size, 0))
	{
		CryptReleaseContext(hProv, 0);
		CryptDestroyHash(hHash);
		return NULL;
	}

	cbHash = MD5LEN;
	if(!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
	{
		CryptReleaseContext(hProv, 0);
		CryptDestroyHash(hHash);
		return NULL;
	}

	char* MD5Char = new char[MD5LEN * 2 + 1];
	memset(MD5Char, 0, MD5LEN * 2 + 1);
	for(DWORD i = 0; i < cbHash; i++)
	{
		MD5Char[i * 2] = rgbDigits[rgbHash[i] >> 4];
		MD5Char[i * 2 + 1] = rgbDigits[rgbHash[i] & 0xf];
	}

	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);
	return MD5Char;

#else
	MD5 md5;
	md5.update(buffer.data, buffer.size);
	string szMD5 = md5.toString();
	char* MD5Char = new char[szMD5.length() + 1];
	memcpy(MD5Char, szMD5.c_str(), szMD5.length() + 1);
	return MD5Char;
#endif // _USING_WINCRYPT
}

long long MD5HashHelper::GetDataBufferHashInt64(const tDataBuffer& buffer)
{
	char* sz = GetDataBufferHashString(buffer);
	if(sz == NULL)
		return 0;
	long long value = GetHashValue1(sz);
	ReleaseHashString(sz);
	return value;
}

void MD5HashHelper::GetDataBufferHashInt128(const tDataBuffer& buffer, long long& first64, long long& second64)
{
	char* sz = GetDataBufferHashString(buffer);
	if(sz == NULL)
		return;
	first64 = GetHashValue1(sz);
	second64 = GetHashValue2(sz);
	ReleaseHashString(sz);
}

char* MD5HashHelper::GetFileHashString(const char* path)
{
	if(path == NULL)
		return NULL;
	
#ifdef _USING_WINCRYPT
	BOOL bResult = FALSE;
	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	HANDLE hFile = NULL;
	BYTE rgbFile[BUFSIZE];
	DWORD cbRead = 0;
	BYTE rgbHash[MD5LEN];
	DWORD cbHash = 0;

	hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
						OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

	if(INVALID_HANDLE_VALUE == hFile)
		return NULL;

	// Get handle to the crypto provider
	if(!CryptAcquireContext(&hProv, NULL, NULL,
							PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
	{
		CloseHandle(hFile);
		return NULL;
	}

	if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
	{
		CloseHandle(hFile);
		CryptReleaseContext(hProv, 0);
		return NULL;
	}

	while(bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, NULL))
	{
		if(0 == cbRead)
		{
			break;
		}

		if(!CryptHashData(hHash, rgbFile, cbRead, 0))
		{
			CryptReleaseContext(hProv, 0);
			CryptDestroyHash(hHash);
			CloseHandle(hFile);
			return NULL;
		}
	}

	if(!bResult)
	{
		CryptReleaseContext(hProv, 0);
		CryptDestroyHash(hHash);
		CloseHandle(hFile);
		return NULL;
	}

	cbHash = MD5LEN;
	if(!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
	{
		CryptReleaseContext(hProv, 0);
		CryptDestroyHash(hHash);
		CloseHandle(hFile);
		return NULL;
	}
	char* MD5Char = new char[MD5LEN * 2 + 1];
	memset(MD5Char, 0, MD5LEN * 2 + 1);
	for(DWORD i = 0; i < cbHash; i++)
	{
		MD5Char[i * 2] = rgbDigits[rgbHash[i] >> 4];
		MD5Char[i * 2 + 1] = rgbDigits[rgbHash[i] & 0xf];
	}

	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);
	CloseHandle(hFile);
	return MD5Char;
#else
	FILE* file = fopen(path, "r");
	fseek(file, 0, SEEK_END);
	long size = ftell(file);
	fseek(file, 0, SEEK_SET);
	tDataBuffer buffer;
	buffer.data = new byte[size];
	buffer.size = size;
	fread(buffer.data, sizeof(byte), size, file);
	fclose(file);
	return GetDataBufferHashString(buffer);
#endif // _USING_WINCRYPT
}

long long MD5HashHelper::GetFileHashInt64(const char* path)
{
	char* sz = GetFileHashString(path);
	if(sz == NULL)
		return 0;
	long long value = GetHashValue1(sz);
	ReleaseHashString(sz);
	return value;
}

bool MD5HashHelper::CheckDataBufferHash(const tDataBuffer& buffer, const char* MD5Char)
{
	char* hash = GetDataBufferHashString(buffer);
	if(hash == NULL)
		return false;
#ifndef _WINDOWS
	bool ret = strncasecmp(hash, MD5Char, strlen(hash)) == 0;
#else
	bool ret = stricmp(hash, MD5Char) == 0;
#endif
	ReleaseHashString(hash);
	return ret;
}

bool MD5HashHelper::CheckFileHash(const char* path, const char* MD5Char)
{
	char* hash = GetFileHashString(path);
	if(hash == NULL)
		return false;
	bool ret = strcmp(hash, MD5Char) == 0;
	ReleaseHashString(hash);
	return ret;
}

void MD5HashHelper::ReleaseHashString(char* MD5Char)
{
	if(MD5Char != NULL)
		SAFE_DELETE_ARRAY(MD5Char);
}

LIB_UTIL_NAMESPACE_END
